package org.cx.comparator.compare;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.cx.comparator.ComparatorConfiguration;
import org.cx.comparator.ComparatorException;
import org.cx.comparator.ComparatorMethod;
import org.cx.comparator.ComparatorResult;
import org.cx.comparator.accessor.ObjectAccessor;
import org.cx.comparator.accessor.ReflectionAsmObjectAccessor;
import org.cx.comparator.annotations.ComparatorDynamicProvider;
import org.cx.comparator.annotations.IDynamicComparatorProvider;
import org.cx.comparator.compare.mode.ComparatorMode;
import org.cx.comparator.compare.mode.ComparatorModeContext;
import org.cx.comparator.compare.mode.ObjectCompareContext;
import org.cx.comparator.expression.ExpressionInvoker;
import org.cx.comparator.expression.QLExpressionInvoker;
import org.cx.comparator.map.ComparatorFieldMapping;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;


@Slf4j
public class ReflectAsmCompare implements ObjectCompare {

    private final ObjectAccessor objectAccessor = new ReflectionAsmObjectAccessor();
    private final ComparatorConfiguration comparatorConfiguration;
    private final ExpressionInvoker expressionInvoker = QLExpressionInvoker.INSTANCE;


    public ReflectAsmCompare(ComparatorConfiguration comparatorConfiguration) {
        this.comparatorConfiguration = comparatorConfiguration;
    }

    @Override
    public List<ComparatorResult> compare(ObjectCompareContext compareContext) {
//        final Object source = compareContext.getSource();
//        final Object target = compareContext.getTarget();
        final Method method = compareContext.getComparatorMethod().getMethod();
        final Class<?> returnType = method.getReturnType();
        if (returnType != List.class) {
            throw new ComparatorException("暂时返回值只支持List。");
        }

        List<ComparatorResult> compareResults = new ArrayList<>();
        final ComparatorDynamicProvider compareProvider = AnnotatedElementUtils.findMergedAnnotation(method, ComparatorDynamicProvider.class);
        Map<String, Map<String, String>> nestedFields = new HashMap<>();
        final Set<ComparatorFieldMapping> comparatorFieldMappings;
        if (compareProvider == null) {
            comparatorFieldMappings = compareContext.getComparatorMethod().getComparatorMap().getComparatorFieldMappings();
        } else {
            IDynamicComparatorProvider comparatorProvider = comparatorConfiguration.getApplicationContext().getBean(compareProvider.provider());
            comparatorFieldMappings = comparatorProvider.provide(compareContext);
            nestedFields = comparatorProvider.nestedFields();
        }

//        final Class<T> rowType = (Class<T>) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0];
//            if (!ComparatorResult.class.isAssignableFrom(rowType)) {
//                throw new ComparatorException("泛型类型必须是ComparatorResult的子类！");
//            }
//            ConstructorAccess<T> access = ReflectAsmUtils.getConstructorAccess(rowType);
        doCompare(compareContext, compareResults, comparatorFieldMappings, nestedFields);
        return compareResults;
    }


    private void doCompare(ObjectCompareContext compareContext, List<ComparatorResult> compareResults, Set<ComparatorFieldMapping> comparatorFieldMappings, Map<String, Map<String, String>> nestedFields) {

        final Object source = compareContext.getSource();
        final Object target = compareContext.getTarget();

        if (source == null || target == null) {
            return;
        }

        if (source instanceof List && target instanceof List) {

            log.debug("begin compare list...");

            int sourceSize = ((List) source).size();
            int targetSize = ((List) target).size();

            if (sourceSize != targetSize) {
                log.warn("List size is not equal!!!");
            }

            for (int i = 0; i < sourceSize; i++) {

                compareContext.setSource(((List) source).get(i));
                compareContext.setTarget(((List) target).get(i));
                doCompare(compareContext, compareResults, comparatorFieldMappings, nestedFields);
            }
        } else {

            for (ComparatorFieldMapping comparatorFieldMapping : comparatorFieldMappings) {

                final String sourceFieldName = comparatorFieldMapping.getSource();
                final Object sourceVal = objectAccessor.getFieldValue(source, sourceFieldName);

                final String targetFieldName = comparatorFieldMapping.getTarget();
                final Object targetVal = objectAccessor.getFieldValue(target, targetFieldName);

                if (nestedFields.containsKey(targetFieldName)) {

                    Map<String, String> nestedFieldsMapping = nestedFields.get(targetFieldName);

                    Set<ComparatorFieldMapping> fieldMappings = buildComparatorFieldMappings(nestedFieldsMapping);

                    compareContext.setSource(sourceVal);
                    compareContext.setTarget(targetVal);
                    doCompare(compareContext, compareResults, fieldMappings, nestedFields);
                    continue;
                }

                final String notEqualsSourceVal = comparatorFieldMapping.getNotEqualsSourceVal();
                final String notEqualsTargetVal = comparatorFieldMapping.getNotEqualsTargetVal();
                final Class<? extends ComparatorMode> equatorModeCls = comparatorFieldMapping.getComparatorType().getEquatorModeCls();
                final ComparatorMode comparatorMode = comparatorConfiguration.getApplicationContext().getBean(equatorModeCls);
                if (!comparatorMode.equatorMode(new ComparatorModeContext(sourceVal, targetVal, comparatorConfiguration, comparatorFieldMapping,
                        compareContext.getComparatorMethod(), compareContext.getInvokeMethodArgs()))) {
                    ComparatorResult compareResult = new ComparatorResult();
                    compareResult.setSourceField(sourceFieldName);
                    if (StringUtils.isBlank(notEqualsSourceVal)) {
                        compareResult.setSourceValue(sourceVal);
                    } else {
                        compareResult.setSourceValue(invokeExpression(compareContext, comparatorFieldMapping.getNotEqualsSourceVal()));
                    }
                    compareResult.setTargetField(targetFieldName);
                    if (StringUtils.isBlank(notEqualsTargetVal)) {
                        compareResult.setTargetValue(targetVal);
                    } else {
                        compareResult.setTargetValue(invokeExpression(compareContext, comparatorFieldMapping.getNotEqualsTargetVal()));
                    }
                    compareResult.setFieldName(comparatorFieldMapping.getFieldName());
                    compareResults.add(compareResult);
                }
            }
        }

    }


    private Set<ComparatorFieldMapping> buildComparatorFieldMappings(Map<String, String> nestedFieldsMapping) {

        Set<ComparatorFieldMapping> comparatorFieldMappings = nestedFieldsMapping.entrySet().stream().map(mapping -> {

            String fieldName = mapping.getValue();
            String field = mapping.getKey();
            return new ComparatorFieldMapping(field, field, fieldName);
        }).collect(Collectors.toSet());

        return comparatorFieldMappings;
    }


    private Object invokeExpression(ObjectCompareContext objectCompareContext, String expression) {
        Map<String, Object> args = new HashMap<>(2);
        args.put("source", objectCompareContext.getSource());
        args.put("target", objectCompareContext.getTarget());

        final ComparatorMethod comparatorMethod = objectCompareContext.getComparatorMethod();
        final Map<Integer, String> params = comparatorMethod.getParams();
        for (Map.Entry<Integer, String> entry : params.entrySet()) {
            args.put(entry.getValue(), objectCompareContext.getInvokeMethodArgs()[entry.getKey()]);
        }

        return expressionInvoker.invoke(expression, args);
    }
}
